/***************************************************************************
 *   Copyright (C) 2006 by latarro   *
 *   glatarow@bastardo   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "gjpeg2khandler.h"

#include <QtGlobal>
#include <QRgb>
#include <QImage>
#include <QVariant>
#include <QFile>
#include <QColor>

// -------------------------------------------
// OpenJPEG stuff
extern "C" {
// mingw includes rpcndr.h but does not define boolean
#if defined(Q_OS_WIN) && defined(Q_CC_GNU)
#   if defined(__RPCNDR_H__) && !defined(boolean)
        typedef unsigned char boolean;
#       define HAVE_BOOLEAN
#   endif
#endif

#include "libopenjpeg/openjpeg.h"
} // extern "C"



// -------------------------------------------
// OpenJPEG stuff
// ---------------------------------------------------------------------------
/*!
 * Divide an integer by a power of 2 and round upwards.
 */
// -------------------- int_ceildivpow2() ------------------------------------
static inline int int_ceildivpow2(int a, int b) {
    return (a + (1 << b) - 1) >> b;
}
// ---------------------------------------------------------------------------


// ---------------------------------------------------------------------------
/*!
* Reads an image from the \e device and decodes it to a \e opj_image structure
* using \e decoder_format.
*/
// ----------------------- DecodeJPEG2k() ------------------------------------
opj_image_t *DecodeJPEG2k(QIODevice *device, CODEC_FORMAT decoder_format = CODEC_JP2)
{
    opj_dparameters_t parameters;   // decompression parameters
    opj_image_t *jp2Image = NULL;   // decompressed image
    opj_dinfo_t* dinfo = NULL;      // handle to a decompressor
    opj_cio_t *cio = NULL;          // OpenJPEG byte memory stream

    	// guess data format
    
    QByteArray prefix = device->peek(16);
        // chceck if it contains JPEG 2000 magic
    if ( prefix.contains("\x0d\x0a\x87\x0a") )
    {
    	decoder_format = CODEC_JP2;
    }
    	// check jpeg codestream header
    else if ( prefix.contains("\xff\x4f\xff\x51") )
    {
    	decoder_format = CODEC_J2K;
    }
    // unknonw format
    else
    {
    	qWarning("Unknown jpeg2k format");
    	return jp2Image;
    }

        // read the input file and put it in memory
    QByteArray src;
    src.resize(device->size());
    QDataStream in(device);
    if(!(in.readRawData(src.data(), device->size()) == device->size())) {
        return jp2Image;
    }

    // ----------------------------------
    // decode the code-stream
    // JPEG 2000 compressed image data

        // set decoding parameters to default values
    opj_set_default_decoder_parameters(&parameters);
        // get a decoder handle
    dinfo = opj_create_decompress(decoder_format);
        // setup the decoder decoding parameters
    opj_setup_decoder(dinfo, &parameters);
        // open a byte stream
    cio = opj_cio_open((opj_common_ptr)dinfo, (unsigned char *)src.data(), src.size());
        // decode the stream and fill the image structure
    jp2Image = opj_decode(dinfo, cio);
        // close the byte stream
    opj_cio_close(cio);
        // free the memory containing the source data
    src.clear();

        // return decoded image
    return jp2Image;
}
// ---------------------------------------------------------------------------


// ------------------------ GJpeg2kHandler() ---------------------------------
GJpeg2kHandler::GJpeg2kHandler()
{
}
// ---------------------------------------------------------------------------


// ------------------------ ~GJpeg2kHandler() --------------------------------
GJpeg2kHandler::~GJpeg2kHandler()
{
}
// ---------------------------------------------------------------------------


// ---------------------------- canRead() ------------------------------------
bool GJpeg2kHandler::canRead() const
{
    if(canRead(device())) {
        setFormat("jp2");
        return true;
    }
    return false;
}
// ---------------------------------------------------------------------------


// ----------------------------- canRead() -----------------------------------
bool GJpeg2kHandler::canRead(QIODevice *device)
{
        // pickup 16 bytes from device
    QByteArray prefix = device->peek(16);
        // chceck if it contains JPEG 2000 magic
    if ( prefix.contains("\x0d\x0a\x87\x0a") )
    {
    	return true;
    }
    	// check jpeg codestream header
    else return prefix.contains("\xff\x4f\xff\x51");
}
// ---------------------------------------------------------------------------


// ----------------------------- read() --------------------------------------
bool GJpeg2kHandler::read(QImage *image)
{
    opj_image_t *jp2Image = NULL;   // decoded image
    QImage *result;                 // converted image
    
        // read decoded image
    if((jp2Image = DecodeJPEG2k(device())) == NULL) {
    	qWarning("error decoding jpeg");
        return false;
    }

    int width, height;  // image width and height
    int wr, hr;         // taken from OpenJPEG

        // currently supports only 24-bit colored pictures
    if (jp2Image->numcomps == 3 /*&& jp2Image->comps[0].dx == jp2Image->comps[1].dx
        && jp2Image->comps[1].dx == jp2Image->comps[2].dx
        && jp2Image->comps[0].dy == jp2Image->comps[1].dy
        && jp2Image->comps[1].dy == jp2Image->comps[2].dy */
        && jp2Image->comps[0].prec == 8  && jp2Image->comps[1].prec == 8 && jp2Image->comps[2].prec == 8
        )
        {

            // decode image width and height
        width = jp2Image->comps[0].w;
        wr = int_ceildivpow2(jp2Image->comps[0].w, jp2Image->comps[0].factor);
        height = jp2Image->comps[0].h;
        hr = int_ceildivpow2(jp2Image->comps[0].h, jp2Image->comps[0].factor);

            // alloc memory for a new image
        result = new QImage(wr, hr, QImage::Format_RGB32);

            // get a pointer to the first pixel data in result image
        QRgb *bits = (QRgb *)result->bits();
        for(int i = 0; i < wr * hr; i++) {
            unsigned char R, G, B;
            
            // calculate coords
            int x = (i) % (wr);
            int y = height - (i) / (wr) - 1;
            #define idx( _x, _y ) ( _x + _y * width )
            
            R = jp2Image->comps[0].data[ idx( x, y ) ];
            G = jp2Image->comps[1].data[ idx( x, y ) ];
            B = jp2Image->comps[2].data[ idx( x, y ) ];
            
                // fill the bit with read pallete
            bits[i] = qRgb(R, G, B);
        }
        
            // free image data structure
        opj_image_destroy(jp2Image);
            // mirror the decoded picture and return the image
            // read pictures are mirrored, yet, don't know why
        *image = result->mirrored();

            // all went good 
        return true;
    }
        // color pallette is bad
    qWarning("error converting jpeg to QImage");
    return false;
}
// ---------------------------------------------------------------------------


// ---------------------------- write() --------------------------------------
bool GJpeg2kHandler::write(const QImage & /*image*/)
{
        return false;
}
// ---------------------------------------------------------------------------


// -------------------------- supportsOption() -------------------------------
bool GJpeg2kHandler::supportsOption(ImageOption /*option*/) const
{
    return false;
}

// ---------------------------------------------------------------------------
    
// ----------------------- option() ------------------------------------------
QVariant GJpeg2kHandler::option(ImageOption /*option*/) const
{
    return QVariant();
}
// ---------------------------------------------------------------------------


// ------------------------- setOption() -------------------------------------
void GJpeg2kHandler::setOption(ImageOption /*option*/, const QVariant & /*value*/)
{
}
// ---------------------------------------------------------------------------







